﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;

#if SILVERLIGHT
using System.Windows;
#endif

namespace Service4u2.Common
{
    /// <summary>
    /// A simple enumeration for determining the method for the webclient to use when requesting data.
    /// </summary>
    public enum HttpMethod
    {
        /// <summary>
        /// 
        /// </summary>
        GET = 0,
        /// <summary>
        /// 
        /// </summary>
        POST = 1
    }

    /// <summary>
    /// Type of content.
    /// </summary>
    public enum ContentType
    {
        /// <summary>
        /// 
        /// </summary>
        UrlEncoded = 0,
        /// <summary>
        /// 
        /// </summary>
        JSON = 1
    }

    /// <summary>
    /// A base class for Json related services.
    /// </summary>
    /// <typeparam name="TResultType">The type of the result.</typeparam>
    public abstract class BaseDataService<TResultType> : IAsyncService<TResultType>
    {
        protected HttpWebRequest request;

        /// <summary>
        /// Occurs when [got result].
        /// </summary>
        public event EventHandler<EventArgs<TResultType>> GotResult;

        /// <summary>
        /// Occurs when [got error].
        /// </summary>
        public event EventHandler<EventArgs<Exception>> GotError;

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseJsonService&lt;TResultType&gt;"/> class.
        /// </summary>
        public BaseDataService()
        {

        }

        private void HandleHttpRequestResponse(HttpWebRequest request, IAsyncResult ar)
        {
            if (!request.HaveResponse)
            {
                HandleError(new InvalidOperationException("The request did not have a response when trying to process it."));
                return;
            }

            string data = string.Empty;
            try
            {
                data = GetResponseData(request, ar);
            }
            catch (Exception ex)
            {
                HandleError(ex);
                return;
            }

            if (!String.IsNullOrEmpty(data))
            {
                HandleResultString(data);
            }
            else
            {
                HandleError(new InvalidOperationException("The response string was empty"));
                return;
            }
        }

        private string GetResponseData(HttpWebRequest request, IAsyncResult ar)
        {
            var response = request.EndGetResponse(ar);
            string data = string.Empty;
            using (StreamReader sr = new StreamReader(response.GetResponseStream()))
            {
                data = sr.ReadToEnd();
            }

            return data;
        }

        /// <summary>
        /// Handles the client error.
        /// </summary>
        /// <param name="exception">The exception.</param>
        protected virtual void HandleError(Exception exception)
        {
            Debug.WriteLine("Service4u2: Client Error: \r\n{0}", exception.Message);
            if (GotError != null)
            {
#if SILVERLIGHT
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                    GotError(this, new EventArgs<Exception>() { Argument = exception }));
#else
                GotError(this, new EventArgs<Exception>() { Argument = exception });
#endif
            }
        }

        /// <summary>
        /// Handles the client result string.
        /// </summary>
        /// <param name="downloadedString">The downloaded string.</param>
        protected abstract void HandleResultString(string downloadedString);
        
        protected virtual void OnGotResult(TResultType result)
        {
            if (GotResult != null)
                GotResult(this, new EventArgs<TResultType>() { Argument = result });
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <param name="uriString">The URI string.</param>
        protected void StartServiceCall(string uriString)
        {
            StartServiceCall(new Uri(uriString));
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <param name="uriString">The URI string.</param>
        /// <param name="method">The method.</param>
        protected void StartServiceCall(string uriString, HttpMethod method)
        {
            StartServiceCall(new Uri(uriString), method);
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <param name="uri">The URI.</param>
        protected void StartServiceCall(Uri uri)
        {
            StartServiceCall(uri, HttpMethod.GET);
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <param name="serviceUri">The service URI.</param>
        /// <param name="method">The method.</param>
        protected virtual void StartServiceCall(Uri serviceUri, HttpMethod method)
        {
            StartServiceCall(serviceUri, method, ContentType.UrlEncoded, string.Empty);
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <param name="serviceUri">The service URI.</param>
        /// <param name="postData">The post data.</param>
        protected virtual void StartServiceCall(Uri serviceUri, Dictionary<string, object> postData)
        {
            StartServiceCall(serviceUri, HttpMethod.POST, ContentType.UrlEncoded, postData.Postify());
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <typeparam name="TPostType">The type of the post type.</typeparam>
        /// <param name="serviceUri">The service URI.</param>
        /// <param name="postData">The post data.</param>
        protected virtual void StartServiceCall<TPostType>(Uri serviceUri, TPostType postData)
            where TPostType : class
        {
            StartServiceCall(serviceUri, HttpMethod.POST, ContentType.UrlEncoded, postData.Postify());
        }

        protected virtual void StartServiceCall(string serviceUri, HttpMethod method, string postData)
        {
            StartServiceCall(new Uri(serviceUri), method, ContentType.UrlEncoded, postData);
        }

        protected virtual void StartServiceCall(string serviceUri, HttpMethod method, ContentType type, string postData)
        {
            StartServiceCall(new Uri(serviceUri), method, type, postData);
        }

        protected virtual void StartServiceCall(Uri serviceUri, HttpMethod method, string postData)
        {
            StartServiceCall(serviceUri, method, ContentType.UrlEncoded, postData);
        }

        /// <summary>
        /// Starts the service call.
        /// </summary>
        /// <param name="serviceUri">The service URI.</param>
        /// <param name="method">The method.</param>
        /// <param name="postData">The post data.</param>
        protected virtual void StartServiceCall(Uri serviceUri, HttpMethod method, ContentType type, string postData)
        {
            request = (HttpWebRequest)WebRequest.Create(serviceUri);

            request.Method = method.ToString();

            if (method == HttpMethod.GET)
            {
                // Remove the content type header, in case it messes with our get.
                if (request.ContentType != null)
                    request.ContentType = null;

                Debug.WriteLine(string.Format("Service4u2: GET Call to {0}", serviceUri));


                request.BeginGetResponse(new AsyncCallback(ar =>
                {
                    HandleHttpRequestResponse(request, ar);
                }), null);
            }
            else
            {
                switch (type)
                {
                    case ContentType.UrlEncoded:
                        request.ContentType = "application/x-www-form-urlencoded";
                        break;
                    case ContentType.JSON:
                        request.ContentType = "application/json";
                        break;
                    default:
                        request.ContentType = "application/x-www-form-urlencoded";
                        break;
                }

                Debug.WriteLine(string.Format("Service4u2: POST Call to {0}\r\nContent Type: {2}\r\nData: {1}", serviceUri, postData, request.ContentType));

                request.BeginGetRequestStream(new AsyncCallback(ar =>
                {
                    try
                    {
                        // Write our data to the request stream.
                        using (StreamWriter sw = new StreamWriter(request.EndGetRequestStream(ar)))
                        {
                            sw.Write(postData);
                        }

                        request.BeginGetResponse(new AsyncCallback(arResponse =>
                        {
                            // Read our data from the response stream.
                            HandleHttpRequestResponse(request, arResponse);
                        }), null);
                    }
                    catch (Exception ex)
                    {
                        HandleError(ex);
                        return;
                    }
                }), null);
            }
        }
    }
}
